// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package pkginit

import (
	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/objw"
	"cmd/compile/internal/staticinit"
	"cmd/compile/internal/typecheck"
	"cmd/compile/internal/types"
	"cmd/internal/obj"
	"cmd/internal/src"
)

// MakeInit创建一个合成init函数来处理任何
// 包范围初始化语句。
// 
// TODO（mdempsky）：进入noder，这样基于types2的前端
// 就可以使用Info了。而是按顺序。
func MakeInit() {
	nf := initOrder(typecheck.Target.Decls)
	if len(nf) == 0 {
		return
	}

	// 创建一个包含所有初始化语句的函数。
	base.Pos = nf[0].Pos() // prolog/epilog获取第一个init stmt的行号
	initializers := typecheck.Lookup("init")
	fn := typecheck.DeclFunc(initializers, ir.NewFuncType(base.Pos, nil, nil, nil))
	for _, dcl := range typecheck.InitTodoFunc.Dcl {
		dcl.Curfn = fn
	}
	fn.Dcl = append(fn.Dcl, typecheck.InitTodoFunc.Dcl...)
	typecheck.InitTodoFunc.Dcl = nil

	// Suppress无用的“can-inline”诊断。
	// 只动态调用Init函数。
	fn.SetInlinabilityChecked(true)

	fn.Body = nf
	typecheck.FinishFuncBody()

	typecheck.Func(fn)
	ir.WithFunc(fn, func() {
		typecheck.Stmts(nf)
	})
	typecheck.Target.Decls = append(typecheck.Target.Decls, fn)

	// 在Inits前面，所以它首先运行，在任何用户声明init 
	// 函数之前。
	typecheck.Target.Inits = append([]*ir.Func{fn}, typecheck.Target.Inits...)

	if typecheck.InitTodoFunc.Dcl != nil {
		// 如果有包作用域初始化语句，我们只使用InitTodoFunc生成temp，所以
		// 如果我们到了这里，有些奇怪。
		base.Fatalf("InitTodoFunc still has declarations")
	}
	typecheck.InitTodoFunc = nil
}

// 任务生成并返回包的初始化记录。
// 参见runtime/proc。go:initTask的布局。
// 初始化的三个任务是：
// 1）初始化当前包所依赖的所有包。
// 2）初始化所有具有初始值设定项的变量。
// /3）运行任何初始化函数。
func Task() *ir.Name {
	var deps []*obj.LSym // 包的初始化任务记录当前包依赖于
	var fns []*obj.LSym  // 函数来调用包初始化

	// 查找包含初始化任务的导入包。
	for _, pkg := range typecheck.Target.Imports {
		n := typecheck.Resolve(ir.NewIdent(base.Pos, pkg.Lookup(".inittask")))
		if n.Op() == ir.ONONAME {
			continue
		}
		if n.Op() != ir.ONAME || n.(*ir.Name).Class != ir.PEXTERN {
			base.Fatalf("bad inittask: %v", n)
		}
		deps = append(deps, n.(*ir.Name).Linksym())
	}

	// 记录用户初始化函数。
	for _, fn := range typecheck.Target.Inits {
		if fn.Sym().Name == "init" {
			// 用于初始化包作用域的合成初始化函数
			// 变量。我们可以使用staticinit优化静态
			// 分配。
			s := staticinit.Schedule{
				Plans: make(map[ir.Node]*staticinit.Plan),
				Temps: make(map[ir.Node]*ir.Name),
			}
			for _, n := range fn.Body {
				s.StaticInit(n)
			}
			fn.Body = s.Out
			ir.WithFunc(fn, func() {
				typecheck.Stmts(fn.Body)
			})

			if len(fn.Body) == 0 {
				fn.Body = []ir.Node{ir.NewBlockStmt(src.NoXPos, nil)}
			}
		}

		// 跳过带有空体的初始化函数。
		if len(fn.Body) == 1 {
			if stmt := fn.Body[0]; stmt.Op() == ir.OBLOCK && len(stmt.(*ir.BlockStmt).List) == 0 {
				continue
			}
		}
		fns = append(fns, fn.Nname.Linksym())
	}

	if len(deps) == 0 && len(fns) == 0 && types.LocalPkg.Name != "main" && types.LocalPkg.Name != "runtime" {
		return nil // 无需初始化
	}

	// 进行初始化。初始化任务结构。
	sym := typecheck.Lookup(".inittask")
	task := typecheck.NewName(sym)
	task.SetType(types.Types[types.TUINT8]) // 假类型
	task.Class = ir.PEXTERN
	sym.Def = task
	lsym := task.Linksym()
	ot := 0
	ot = objw.Uintptr(lsym, ot, 0) // 状态：尚未初始化
	ot = objw.Uintptr(lsym, ot, uint64(len(deps)))
	ot = objw.Uintptr(lsym, ot, uint64(len(fns)))
	for _, d := range deps {
		ot = objw.SymPtr(lsym, ot, d, 0)
	}
	for _, f := range fns {
		ot = objw.SymPtr(lsym, ot, f, 0)
	}
	// 初始化任务有指针，但没有指针进入Go堆。
	// 它不是完全只读的，状态字段必须是可修改的。
	objw.Global(lsym, int32(ot), obj.NOPTR)
	return task
}
